package com.ml.netty.demo;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.channel.unix.DomainSocketAddress;
import io.netty.handler.codec.http.*;
import io.netty.handler.codec.http.websocketx.*;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.stream.ChunkedWriteHandler;
import io.netty.util.CharsetUtil;
import lombok.extern.slf4j.Slf4j;

import java.net.URI;
import java.net.URISyntaxException;

/**
 * 参考资料：https://www.cnblogs.com/daijiting/p/12092202.html
 * 带握手版
 * @author dzx
 * @ClassName:
 * @Description: netty客户端
 * @date 2023年06月30日 21:30:02
 */
@Slf4j
public class QQSocketClient {
    // 服务端IP
    static final String url = "wss://sandbox.api.sgroup.qq.com/websocket";
 
    // 主函数启动
    public static void main(String[] args) throws InterruptedException, URISyntaxException {
        sendMessage();
    }
    /**
     * 核心方法（处理：服务端向客户端发送的数据、客户端向服务端发送的数据）
     */
    public static void sendMessage() throws InterruptedException, URISyntaxException {
            // 创建事件循环组和通道类型
            EventLoopGroup group = new NioEventLoopGroup();
        try {
                // 创建引导程序并设置参数
                Bootstrap bootstrap = new Bootstrap();
                bootstrap.group(group)
                        .channel(NioSocketChannel.class)
                        .option(ChannelOption.SO_KEEPALIVE, true)
                        .option(ChannelOption.TCP_NODELAY, true)
                        .handler(new LoggingHandler(LogLevel.INFO))
                        .remoteAddress(new DomainSocketAddress(url))
                        .handler(new ChannelInitializer<SocketChannel>() {
                            @Override
                            public void initChannel(SocketChannel ch) throws Exception {
                                // 添加协议处理器和帧处理器等
//                                WebSocketClientHandshaker webSocketClientHandshaker = WebSocketClientHandshakerFactory.newHandshaker(new URI(url),
//                                        WebSocketVersion.V13, null, true, HttpHeaders.EMPTY_HEADERS, 10000);
                                ChannelPipeline entries = ch.pipeline()
                                        .addLast(new HttpClientCodec())
                                        .addLast(new HttpObjectAggregator(8192))
                                        .addLast(new ChunkedWriteHandler())
//                                        .addLast(new WebSocketClientProtocolHandler(webSocketClientHandshaker))
                                        .addLast("hookedHandler", new NettyWebSocketClientHandler());// 自定义的帧处理器类，用于处理WebSocket帧数据

                            }
                        });


            //websocke连接的地址，/hello是因为在服务端的websockethandler设置的
            URI websocketURI = new URI("wss://sandbox.api.sgroup.qq.com:443/websocket");
            HttpHeaders httpHeaders = new DefaultHttpHeaders();
            //进行握手
            WebSocketClientHandshaker handshaker = WebSocketClientHandshakerFactory.newHandshaker(websocketURI, WebSocketVersion.V13, (String) null, true, httpHeaders);
            //客户端与服务端连接的通道，final修饰表示只会有一个
//            final Channel channel = bootstrap.connect(websocketURI.getHost(), websocketURI.getPort()).sync().channel();
            final Channel channel = bootstrap.connect(websocketURI.getHost(), websocketURI.getPort()).sync().channel();
            NettyWebSocketClientHandler handler = (NettyWebSocketClientHandler) channel.pipeline().get("hookedHandler");
            handler.setHandshaker(handshaker);
            handshaker.handshake(channel);
            //阻塞等待是否握手成功
            handler.handshakeFuture().sync();
            System.out.println("握手成功");


                // 启动客户端连接并等待连接成功或失败的结果
//                ChannelFuture future = bootstrap.connect().sync();
//
//            future.channel().closeFuture().sync();
                // 处理连接成功或失败的情况，例如关闭连接、处理异常等。这里可以根据实际需求进行相应的处理。
            } catch (Exception e) {
            System.out.println("哦和 又异常了");
                e.printStackTrace();
            } finally {
                // 关闭事件循环组和释放资源
                group.shutdownGracefully();
            }
        }

    public static class NettyWebSocketClientHandler extends SimpleChannelInboundHandler<Object> {
        //握手的状态信息
        WebSocketClientHandshaker handshaker;
        //netty自带的异步处理
        ChannelPromise handshakeFuture;

        @Override
        protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
            System.out.println("channelRead0 准备读取");
            // 处理接收到的WebSocket帧数据，例如解析消息、执行逻辑等。这里可以根据实际需求进行相应的处。
            System.out.println("当前握手的状态"+this.handshaker.isHandshakeComplete());
            Channel ch = ctx.channel();
            FullHttpResponse response;
            //进行握手操作
            if (!this.handshaker.isHandshakeComplete()) {
                try {
                    response = (FullHttpResponse)msg;
                    //握手协议返回，设置结束握手
                    this.handshaker.finishHandshake(ch, response);
                    //设置成功
                    this.handshakeFuture.setSuccess();
                    System.out.println("服务端的消息"+response.headers());
                } catch (WebSocketHandshakeException var7) {
                    var7.printStackTrace();
                    FullHttpResponse res = (FullHttpResponse)msg;
                    String errorMsg = String.format("握手失败,status:%s,reason:%s", res.status(), res.content().toString(CharsetUtil.UTF_8));
                    this.handshakeFuture.setFailure(new Exception(errorMsg));
                }
            } else if (msg instanceof FullHttpResponse) {
                response = (FullHttpResponse)msg;
                throw new IllegalStateException("Unexpected FullHttpResponse (getStatus=" + response.status() + ", content=" + response.content().toString(CharsetUtil.UTF_8) + ')');
            } else {
                //接收服务端的消息
                WebSocketFrame frame = (WebSocketFrame)msg;
                //文本信息
                if (frame instanceof TextWebSocketFrame) {
                    TextWebSocketFrame textFrame = (TextWebSocketFrame)frame;
                    System.out.println("客户端接收的消息是:"+textFrame.text());
                }
                //二进制信息
                if (frame instanceof BinaryWebSocketFrame) {
                    BinaryWebSocketFrame binFrame = (BinaryWebSocketFrame)frame;
                    System.out.println("BinaryWebSocketFrame");
                }
                //ping信息
                if (frame instanceof PongWebSocketFrame) {
                    System.out.println("WebSocket Client received pong");
                }
                //关闭消息
                if (frame instanceof CloseWebSocketFrame) {
                    System.out.println("receive close frame");
                    ch.close();
                }

            }

        }
        /**
         * 发送给服务器消息的方法
         * @param ctx
         * @throws Exception
         */
        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
            System.out.println("与服务端连接成功");
//            ctx.writeAndFlush(Unpooled.copiedBuffer("你好，我是客户端", CharsetUtil.UTF_8));
        }
        /**
         * 在处理过程中引发异常时被调用
         * @param ctx
         * @param cause
         * @throws Exception
         */
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            System.out.println("异常关闭");
            cause.printStackTrace();
            ctx.close();
        }

        /**
         * 非活跃状态，没有连接远程主机的时候。
         *
         * @param ctx
         * @throws Exception
         */
        @Override
        public void channelInactive(ChannelHandlerContext ctx) throws Exception {
            System.out.println("主机关闭");
        }

        public void handlerAdded(ChannelHandlerContext ctx) {
            this.handshakeFuture = ctx.newPromise();
        }

        public WebSocketClientHandshaker getHandshaker() {
            return handshaker;
        }

        public void setHandshaker(WebSocketClientHandshaker handshaker) {
            this.handshaker = handshaker;
        }

        public ChannelPromise getHandshakeFuture() {
            return handshakeFuture;
        }

        public void setHandshakeFuture(ChannelPromise handshakeFuture) {
            this.handshakeFuture = handshakeFuture;
        }

        public ChannelFuture handshakeFuture() {
            return this.handshakeFuture;
        }

    }
}